// NBodySimulations.cpp : Defines the entry point for the console application.
//

#include <iostream>
#include <vector>
#include <time.h>
#include <ctime>
#include <SDL.h>
#include <Windows.h>
#include "opencl.h"
#include "NBody.h"

const int NPARTICLE = 0;

#define FPS_LIMIT FALSE
const float FPS_DIV = 1000/60;
int avgFPSCounter = 0;
int partikelCount = 0;
double avgFPS = 60;
bool running = true;

typedef struct border
{
	int minX, maxX,
		  minY, maxY;
	border(int x_, int y_)
	{
		minX = x_;
		maxX = x_;
		minY = y_;
		maxY = y_;
	};
	void setNew(int x_, int y_)
	{
		if (x_ < minX)
			minX = x_;
		else if (x_ > maxX)
			maxX = x_;

		if (y_ < minY)
			minY = y_;
		else if (y_ > maxY)
			maxY = y_;
	}
} border;

cl_float betrag(cl_float a)
{
	if (a < 0)
		return -a;
	return a;
}

int nPartikel = 0;
SDL_Renderer* renderer = NULL;
SDL_Event* event = NULL;
border* global_border = NULL;
SDL_Window* window;
cl_float5* test;
OpenCL* openCL = NULL;

bool autoFPS = false;
int weightMax = 54,
weightMin = 6,
universeWidth = 10000,
universeHeight = 10000,
maxVel = 50;
int stepSize = 100;

void changeStepSize(boolean in)
{
	 if (in)
		stepSize *= 2;
	else
		stepSize /= 2;
	
	if (stepSize < 1)
		stepSize = 1;
	else if (stepSize > 2050)
		stepSize = 2048;

		std::cout << "StepSize: " << stepSize << std::endl;
}

void initSDL()
{
	SDL_Init(SDL_INIT_VIDEO);

	window = SDL_CreateWindow
		(
		"NBody-Simulation",
		GetSystemMetrics(SM_CXSCREEN) - GetSystemMetrics(SM_CYSCREEN) - 100,
		50,
		GetSystemMetrics(SM_CYSCREEN) - 100,
		GetSystemMetrics(SM_CYSCREEN)-100,
		SDL_WINDOW_SHOWN |
		SDL_WINDOW_RESIZABLE
		);
	renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
	event = new SDL_Event();
}

long start = clock();
char buffer[50];
unsigned int interval = 10;
void generateNewPartikel(int _nPartikel)
{
	cl_float5* newTest = new cl_float5[_nPartikel];
	for (int i = 0; i < _nPartikel; i++)
	{
		if (i < nPartikel)
		{
			newTest[i] = test[i];
			continue;
		}

		newTest[i].x = (cl_float)(rand() / (cl_float)0x7fff * universeWidth);
		newTest[i].y = (cl_float)(rand() / (cl_float)0x7fff * universeHeight);
		newTest[i].w = (cl_float)(rand() / (cl_float)0x7fff * weightMax) + weightMin;
		newTest[i].velX = 0;
		newTest[i].velY = 0;
	}
	test = newTest;
	nPartikel = _nPartikel;

	std::vector<OpenCLWork*> worker = openCL->getWorker();
	for (int i = 0; i < worker.size(); i++)
	{
		((NBody*)(worker.at(i)))->change(test, nPartikel);
	}
}

void SDLpaint(cl_float5* particles, border* borders)
{
	long now = clock();
	double fps = (1000.0 / (now - start));
	if (isinf(avgFPS))
		avgFPS = 0;
	avgFPS += fps;
	std::vector<OpenCLWork*> worker;
	while (SDL_PollEvent(event))
	{
		int value;
		if (event->type == SDL_MOUSEWHEEL)
		{
			value = ((SDL_MouseWheelEvent*)event)->y;
			global_border->maxX -= value * 50;
			global_border->maxY -= value * 50;

			global_border->minX += value * 50;
			global_border->minY += value * 50;
		}
		else if (event->type == SDL_KEYDOWN)
		{
			value = event->key.keysym.sym;

			switch (value)
			{
			case SDLK_ESCAPE:
				exit(0);
				break;
			case SDLK_KP_PLUS:
				generateNewPartikel(nPartikel + stepSize);
				break;
			case SDLK_KP_MINUS:
				nPartikel -= stepSize;
				if (nPartikel < 0)
					nPartikel = 1;
				worker = openCL->getWorker();
				for (int i = 0; i < worker.size(); i++)
				{
					((NBody*)(worker.at(i)))->change(test, nPartikel);
				}
				break;
			case SDLK_PAGEDOWN:
				changeStepSize(false);
				break;
			case SDLK_PAGEUP:
				changeStepSize(true);
				break;
			case SDLK_h:
				std::cout << "\nTastaturbelegung:"
					<< std::endl << "ESC = Beenden."
					<< std::endl << "Numlock_+ = Partikelanzahl erhoehen."
					<< std::endl << "Numlock_- = Partikelanzahl verringern."
					<< std::endl << "Bild hoch = Partikel- Veraenderungsgroesse verdoppeln."
					<< std::endl << "Bild runter = Partikel- Veranderungsgroesse halbieren."
					<< std::endl << "Numblock * = Partikelanzahl automatisch anpassen um 60FPS +-2 zu erreichen."
					<< std::endl;
				break;
			case SDLK_KP_MULTIPLY:
				autoFPS = !autoFPS;
				break;
			default:
				std::cout << "Btn without use." << std::endl;
				break;
			}
		}
	}

	if (avgFPSCounter++ >= interval)
	{
		avgFPS /= interval;
		sprintf(buffer, "avgFPS: %.2f PartikelAnzahl %u", avgFPS, partikelCount);
		SDL_SetWindowTitle(window, buffer);
		avgFPSCounter = 0;


		if (autoFPS && avgFPS < 58 && openCL != NULL)
		{
		 	nPartikel -= 100;///= 1.1;
		 	std::vector<OpenCLWork*> worker = openCL->getWorker();
		 	for (int i = 0; i < worker.size(); i++)
		 	{
			 	((NBody*)(worker.at(i)))->change(test, nPartikel);
			}
			std::cout << "Partikel--=" << nPartikel << std::endl;
	 	}
		else if (autoFPS && avgFPS > 62 && openCL != NULL)
		{
			int j = nPartikel + 100;//(avgFPS / 60.0);

			generateNewPartikel(j);

			std::cout << "Partikel++=" << nPartikel << std::endl;
		}
	}
	

	if (global_border == NULL)
		global_border = borders;

	/* Draw a gray background */
	SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
	SDL_RenderClear(renderer);
	SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0x00, 0xFF);

	cl_float verhaltnisX = 580.0f / (global_border->maxX - global_border->minX);
	cl_float verhaltnisY = 580.0f / (global_border->maxY - global_border->minY);
	cl_float minLength = 0;
		
	SDL_RenderSetScale(renderer, verhaltnisX, verhaltnisY);

	for (int i = 1; i < nPartikel; i++) 
	{
		SDL_RenderDrawPoint(renderer, particles[i].x, particles[i].y);
	}
	SDL_RenderPresent(renderer);
	
	partikelCount = nPartikel;

#if FPS_LIMIT
	SDL_Delay((Uint32)FPS_DIV - (now - start));
#endif
	start = clock();
}

void initOpenCL()
{
	OpenCL::printPlatforms();

	std::cout << "Bitte Platform ID eingeben: ";

	char t;
	std::cin >> t;

	if ('0' <= t && t <= '9')
		openCL = new OpenCL(t-'0');

	openCL->printDevices();

	std::cout << "Eingabe wird beendet bei einem zeichen auser 0-9: \n";
	std::vector<byte> devices = std::vector<byte>();

	bool found = false;
	while (!found)
	{
		std::cin >> t;
		if ('0' <= t && t <= '9')
		{
			bool founder = false;
			for (int i = 0; i < devices.size() && !founder; i++)
			{
				if (devices[i] == t - '0')
					founder = true;
			}
			if (!founder)
				devices.push_back(t - '0');
		}
		else if (t == ' ')
			;
		else
			found = true;
	}
	
	openCL->selectDevices(devices);
	std::cout << "Druecke H fuer Hilfe.";
}


int main(int argc, char* argv[])
{
	initOpenCL();
	
	nPartikel = NPARTICLE;
	
	std::vector <int> MaxWorkGroupSize = openCL->getMaxWorkGroupSizes();
	std::vector <int> MaxComputeUnits = openCL->getMaxComputeUnits();

	for (int i = 0; i < MaxWorkGroupSize.size(); i++)
		nPartikel += MaxWorkGroupSize[i] * MaxComputeUnits[i];
	
	universeWidth = GetSystemMetrics(SM_CXSCREEN)-100;
	universeHeight = GetSystemMetrics(SM_CYSCREEN)-100;
	weightMin = nPartikel / 200 + 1;
	weightMax = weightMin;

	time_t t;
	time(&t);
	srand((unsigned int)t);              /* Zufallsgenerator initialisieren */

	test = new cl_float5[nPartikel];
	
 	test[0].x = (cl_float)(universeWidth / 2);
 	test[0].y = (cl_float)(universeHeight / 2);
 	test[0].w = (cl_float)(weightMax+weightMin)*5;
	test[0].velX = 0;
 	test[0].velY = 0;

	for (int i = 1; i < nPartikel; i++)
	{
		test[i].x = (cl_float)(rand() / (cl_float)0x4fff * universeWidth);
		test[i].y = (cl_float)(rand() / (cl_float)0x4fff * universeHeight);
		test[i].w = (cl_float)(rand() / (cl_float)0xffff * weightMax) + weightMin;
		test[i].velX = 0;
		test[i].velY = 0;
	}

	OpenCLWork* worker = (OpenCLWork*) new NBody(test, nPartikel);
	openCL->setWork(worker);

	border* borders;
	borders = new border(universeWidth, universeHeight);
	borders->setNew(50, 50);
	
	std::cout << "\n";
	initSDL();


	while (running)
	{
		openCL->next();
		openCL->waitForWorker();

		SDLpaint(test, borders);
	}
	return 0;
}
